#include "Miro.h"
#include "Scene.h"
#include "Camera.h"
#include "Image.h"
#include "Console.h"
#include "LightPath.h"

#include <vector>
#include <ctime>

#ifdef WIN32
#include <ppl.h>
using namespace Concurrency;
using namespace std;
#endif

Scene * g_scene = 0;
Stats g_stats;
short *wavelengths = 0;

// from Toshiya's PPM code
unsigned int g_num_hash, g_pixel_index, g_num_photon;
double g_hash_s;
List **g_hash_grid;
List *g_hitpoints;
AABB g_hpbbox;

Scene::Scene()
{
	 m_pEnvMap = new EnvMap;
	 m_pBvh = new BVH;
	 g_hitpoints = NULL;
}

Scene::~Scene()
{
	// clean up
	if (m_pEnvMap) delete m_pEnvMap;
	if (m_pBvh) delete m_pBvh;
}

void Scene::openGL(Camera *cam)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    cam->drawGL();

    // draw objects
    for (size_t i = 0; i < m_objects.size(); ++i)
        m_objects[i]->renderGL();

    glutSwapBuffers();
}

void Scene::preCalc()
{
    Objects::iterator it;
    for (it = m_objects.begin(); it != m_objects.end(); it++)
    {
        Object* pObject = *it;
        pObject->preCalc();
    }
    Lights::iterator lit;
    for (lit = m_lights.begin(); lit != m_lights.end(); lit++)
    {
        PointLight* pLight = *lit;
        pLight->preCalc();
    }

    m_pBvh->build(&m_objects);
}

bool Scene::trace(HitInfo& minHit, const Ray& ray, float tMin, float tMax) const
{
#ifdef RAY_STATS
	// accumulate global total
	g_stats.m_totalRays++;
#endif

    return m_pBvh->intersect(minHit, ray, tMin, tMax);
}

void Scene::raytraceImage(Camera *cam, HdrImage *img)
{
    std::clock_t start = std::clock();
	g_stats.reset();

#if defined WIN32
	std::vector<int> width_vector;
	for(int i = 0; i < img->width(); ++i){
		width_vector.push_back(i);
	}
#endif
	
    
    // loop over all pixels in the image
    for (int j = 0; j < img->height(); ++j)
    {

#ifndef PARALLEL 
		for (int i = 0; i < img->width(); ++i){
#elif defined WIN32
		parallel_for_each(width_vector.begin(), width_vector.end(), [&](int i){
#else
#pragma omp parallel for
		for (int i = 0; i < img->width(); ++i){
#endif

			HitInfo hitInfo;
			Vector3 shadeResult(0);
			// first ray is in middle of pixel
            Ray ray = cam->eyeRay(i, j, img->width(), img->height());
			for (int k=0; k<NUM_ANTIALIAS_RAYS; ++k)
			{
				if (trace(hitInfo, ray))
				{
					shadeResult += hitInfo.material->shade(ray, hitInfo, 1, *this, RAYTRACE);
				}
				else
				{
					// environment map it
					ray.d.normalize();
					shadeResult += getMapValue(ray.d);
				}
				ray = cam->randEyeRay(i, j, img->width(), img->height());
			}
			
            img->setPixel(i, j, shadeResult/NUM_ANTIALIAS_RAYS);

#ifndef PARALLEL 
		}
#elif defined WIN32
		});
#else
		}
#endif

        img->drawScanline(j);
        glFinish();
        printf("Rendering Progress: %.3f%%\r", j/float(img->height())*100.0f);
        fflush(stdout);
    }
    
    printf("Rendering Progress: 100.000%%\n");
    debug("done Raytracing!\n");
	double diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
	printf("Completed in a lightning fast: %f seconds\n",diff);

#ifdef RAY_STATS
	printf("total number of rays: %lld\n", g_stats.m_totalRays);
	if (g_stats.m_totalRays)
	{
		printf("avg bounding box intersections per ray: %.2f\n", float(g_stats.m_totalBBoxIntersections)/g_stats.m_totalRays);
		printf("avg triangle intersections per ray: %.2f\n", float(g_stats.m_totalTriangleIntersections)/g_stats.m_totalRays);
	}
#endif
}


void Scene::pathtraceImage(Camera *cam, HdrImage *img)
{
    std::clock_t start = std::clock();
	g_stats.reset();

#if defined WIN32
	std::vector<int> width_vector;
	for(int i = 0; i < img->width(); ++i){
		width_vector.push_back(i);
	}
#endif

	Vector3 b = (0.);
    // loop over all pixels in the image
    for (int j = 0; j < img->height(); ++j) {

#ifndef PARALLEL 
		for (int i = 0; i < img->width(); ++i){
#elif defined WIN32
		parallel_for_each(width_vector.begin(), width_vector.end(), [&](int i){
#else
#pragma omp parallel for
		for (int i = 0; i < img->width(); ++i){
#endif
			HitInfo hitInfo;
			Vector3 shadeResult(0);

			for (int k=0; k<NUM_PATHS; ++k)
			{
				Ray ray = cam->randEyeRay(i, j, img->width(), img->height());
				if (trace(hitInfo, ray))
				{
					shadeResult += hitInfo.material->shade(ray, hitInfo, 1, *this, PATHTRACE);
				}
				else
				{
					// environment map it
					ray.d.normalize();
					shadeResult += getMapValue(ray.d);
				}
			}
			img->setPixel(i, j, pow((shadeResult/NUM_PATHS).x, 1/2.2));
			b += (shadeResult/NUM_PATHS/512/512);

#ifndef PARALLEL 
		}
#elif defined WIN32
		});
#else
		}
#endif


        img->drawScanline(j);
        glFinish();
        printf("Rendering Progress: %.3f%%\r", j/float(img->height())*100.0f);
        fflush(stdout);
    }
    
    printf("Rendering Progress: 100.000%%\n");
    debug("done Raytracing!\n");
	double diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
	printf("Completed in a lightning fast: %f seconds\n",diff);
	printf("b = %.4f\n",b.x);

#ifdef RAY_STATS
	printf("total number of rays: %lld\n", g_stats.m_totalRays);
	if (g_stats.m_totalRays)
	{
		printf("avg bounding box intersections per ray: %.2f\n", float(g_stats.m_totalBBoxIntersections)/g_stats.m_totalRays);
		printf("avg triangle intersections per ray: %.2f\n", float(g_stats.m_totalTriangleIntersections)/g_stats.m_totalRays);
	}
#endif
}


void Scene::metropolisImage(Camera *cam, HdrImage *img)
{
    std::clock_t start = std::clock();
	g_stats.reset();
	int NUM_MUTS = 20000000;
	int w = img->width();
	int h = img->height();
	Vector3 imgArr[w][h];
	float I, oldI, a;
	float x, y, oldx, oldy;
	Vector3 point, oldPoint = Vector3(0.);

	for(int i=0; i<w; ++i)
		for(int j=0; j<h; ++j)
			imgArr[i][j] = Vector3(0.);

	float b = .1304*w*h/((float) NUM_MUTS);

	LightPath oldPath = LightPath(0);
	LightPath newPath = LightPath(0);
	HitInfo hitInfo;

	do{
		oldPath.generate();
		oldx = w*oldPath.Ux;
		oldy = h*oldPath.Uy;
		
		Ray startRay = cam->specificEyeRay(oldx, oldy, w, h);

		if (trace(hitInfo, startRay))
		{
			oldPoint = hitInfo.material->shade(startRay, hitInfo, 1, *this, PATHTRACE);
		}

	} while(oldPoint.x < .0001);

	oldI = oldPoint.mean();
	
	for (int k=0; k<NUM_MUTS; ++k)
	{
		newPath.generate();
		x = w*newPath.Ux;
		y = h*newPath.Uy;

		Ray ray = cam->specificEyeRay(x, y, w, h);
		

		if (trace(hitInfo, ray))
		{
			point = hitInfo.material->shade(ray, hitInfo, 1, *this, PATHTRACE);
			I = point.mean();
			a = std::min(1.0f, I/oldI);
		}
		else
		{
			a = 0;
		}


		if(((float) rand()/RAND_MAX) < a)
		{
			oldPoint = point;
			oldI = I;
			oldx = x;
			oldy = y;
		}

		imgArr[std::min(w-1, (int) oldx)][std::min(h-1, (int) oldy)] += b*oldPoint/oldI;

		if(k%(NUM_MUTS/1000) == 0)
		{
			printf("Rendering Progress: %.1f%%\r", ((float) k)/(NUM_MUTS/100.));
	    		fflush(stdout);
    		}
	}

	for(int i = 0; i < w; ++i)
		for(int j = 0; j < h; ++j)
			img->setPixel(i, j, Vector3(pow(imgArr[i][j].x, 1/2.2), pow(imgArr[i][j].y, 1/2.2), pow(imgArr[i][j].z, 1/2.2)));
    
    printf("Rendering Progress: 100.000%%\n");
    debug("done Raytracing!\n");
	double diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
	printf("Completed in a lightning fast: %f seconds\n",diff);
}


// from Toshiya's PPM code
void Scene::build_hash_grid(const int w, const int h)
{
	g_hpbbox.reset();
	List *lst = g_hitpoints;
	while (lst != NULL)
	{
		HPoint *hp = lst->id;
		lst = lst->next;
		g_hpbbox.fit(hp->pos);
	}
	Vector3 ssize = g_hpbbox.max - g_hpbbox.min; // compute initial radius
	double irad = ((ssize.x + ssize.y + ssize.z) / 3.0) / ((w + h) / 2.0) * 2.0;
	g_hpbbox.reset();
	lst = g_hitpoints;
	int vphoton = 0; // determine hash size
	while (lst != NULL)
	{
		HPoint *hp = lst->id;
		lst = lst->next;
		hp->r2 = irad * irad;
		hp->n = 0;
		hp->flux = Vector3();
		vphoton++;
		g_hpbbox.fit(hp->pos - irad);
		g_hpbbox.fit(hp->pos + irad);
	}
	g_hash_s = 1.0 / (irad * 2.0);
	g_num_hash = vphoton;
	g_hash_grid = new List*[g_num_hash];
	for (unsigned int i = 0; i < g_num_hash; i++)
		g_hash_grid[i] = NULL;
	lst = g_hitpoints;
	while (lst != NULL)
	{ // store hitpoints in the hashed grid
		HPoint *hp = lst->id;
		lst = lst->next;
		Vector3 BMin = ((hp->pos - irad) - g_hpbbox.min) * g_hash_s;
		Vector3 BMax = ((hp->pos + irad) - g_hpbbox.min) * g_hash_s;
		for (int iz = abs(int(BMin.z)); iz <= abs(int(BMax.z)); iz++)
		{
			for (int iy = abs(int(BMin.y)); iy <= abs(int(BMax.y)); iy++)
			{
				for (int ix = abs(int(BMin.x)); ix <= abs(int(BMax.x)); ix++)
				{
					int hv = hash(ix, iy, iz);
					g_hash_grid[hv] = g_hash_grid[hv]->ListAdd(hp);
				}
			}
		}
	}
}


void Scene::appmImage(Camera *cam, HdrImage *img)
{
    std::clock_t start = std::clock();
	g_stats.reset();

	// measurement point pass
    for (int j = 0; j < img->height(); ++j)
    {
		for (int i = 0; i < img->width(); ++i)
		{
			g_pixel_index = i + j*img->width();

			HitInfo hitInfo;
			Vector3 shadeResult(0);

			Ray ray = cam->eyeRay(i, j, img->width(), img->height());
			for (int k=0; k<NUM_ANTIALIAS_RAYS; ++k)
			{
				if (trace(hitInfo, ray))
				{
					hitInfo.material->shade(ray, hitInfo, 1, *this, APPM_MEAS);
				}
				ray = cam->randEyeRay(i, j, img->width(), img->height());
			}
		}
    }

    g_scene->build_hash_grid(img->width(), img->height());
    printf("%d measurement points\n", g_num_hash);

		// TODO: photon pass

    g_num_photon = NUM_PHOTONS;
#ifndef PARALLEL
    for (int i = 0; i < g_num_photon; ++i){
#elif defined WIN32
    for (int i = 0; i < g_num_photon; ++i){ // I don't know how this should be done in windows
#else
#pragma omp parallel for
    for (int i = 0; i < g_num_photon; ++i){
#endif

    	// random every time for now
    	LightPath lp(LightPath::APPM);
    	int randLight = int(m_areaLights.size() * float(rand())/RAND_MAX);
    	randLight = std::min(int(m_areaLights.size()-1), randLight);
    	m_areaLights[randLight]->tracePhoton(lp, *this);

		if(i % (g_num_photon/100) == 0)
		{
			printf("Rendering Progress: %.1f%%\r", ((float) i)/(g_num_photon/100.));
	    	fflush(stdout);
    	}

#ifndef PARALLEL
    	}
#elif defined WIN32
		});
#else
		}
#endif


    // rendering pass

    // from Toshiya's PPM code
	List* lst = g_hitpoints;
	Vector3 *tempImg = new Vector3[img->width() * img->height()];
	while (lst != NULL)
	{
		HPoint* hp = lst->id;
		lst = lst->next;
		int i = hp->pix;
		if (hp->isLight)
			tempImg[i] = hp->f;
		else
			tempImg[i] = hp->flux * (1.0 / (PI * hp->r2 * g_num_photon));
	}

    for (int j = 0; j < img->height(); ++j)
    {
    	for (int i = 0; i < img->width(); ++i)
    	{
    		int k = i + j*img->width();
    		//img->setPixel(i, j, tempImg[k]);
    		// gamma correct
    		img->setPixel(i, j, Vector3(pow(tempImg[k].x, 1/2.2), pow(tempImg[k].y, 1/2.2), pow(tempImg[k].z, 1/2.2)));
    	}

    	img->drawScanline(j);
        glFinish();
    }

    printf("Rendering Progress: 100.000%%\n");
    debug("done Raytracing!\n");
	double diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
	printf("Completed in a lightning fast: %f seconds\n",diff);

#ifdef RAY_STATS
	printf("total number of rays: %lld\n", g_stats.m_totalRays);
	if (g_stats.m_totalRays)
	{
		printf("avg bounding box intersections per ray: %.2f\n", float(g_stats.m_totalBBoxIntersections)/g_stats.m_totalRays);
		printf("avg triangle intersections per ray: %.2f\n", float(g_stats.m_totalTriangleIntersections)/g_stats.m_totalRays);
	}
#endif
}

